package server;

import java.io.IOException;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Calendar;

import GUI.ResultDisplay;

import ocsf.server.ConnectionToClient;

import logic.Message;
import logic.Order;

public class OrderController {

	private DB_Manager DB;
	private OBL_Server parent;
	
	public OrderController(OBL_Server parent,DB_Manager DB) {
		this.parent = parent;
		this.DB = DB;
	}
	
	
	/**
	 * This function querys the DB for all orders on entered BID
	 * The function will return the UID of the user on the head of the order list
	 * @param bid - the BID to look for
	 * @return UID - the member on the head of the order list, or -1 if no member was found
	 */
	public int getHeadOfOrderList(String bid){
		
		String query = "SELECT * FROM orders WHERE BID = '" + bid + "' AND STATUS = '" + Order.ORDER_OPEN + "'";
		ResultSet rs = DB.Query(query);
		
		ArrayList<String> dates = new ArrayList<String>();
		ArrayList<Integer> uids = new ArrayList<Integer>();
		
		try{
			while(rs.next()){
				dates.add(rs.getString("ORDER_DATE"));
				uids.add(rs.getInt("UID"));
			}
		}catch(SQLException e){
			parent.Log("[getHeadOfOrderList] SQLException. " + e.getMessage());
		}catch(ClassCastException e){
			parent.Log("[getHeadOfOrderList] ClassCastException. " + e.getMessage());
		}
		
		int headUID = -1;
		String headString = "";
		Calendar headCalendar;
		if (dates.size() > 0){
			headUID = uids.get(0);
			headString = dates.get(0);
		}else{
			return headUID;
		}
		
		int year = Integer.parseInt(headString.substring(0, 4));
		int month = Integer.parseInt(headString.substring(5, 7));
		int day = Integer.parseInt(headString.substring(8, headString.length()));
		headCalendar = Calendar.getInstance();
		headCalendar.set(year, month-1, day);
		headCalendar.setLenient(false);
		
		long DayInMillis = 1000 * 60 * 60 * 24; 	//millis * sec * min * hour = 1 day

		
		for(int i=1; i<dates.size(); i++){
			
			year = Integer.parseInt(dates.get(i).substring(0, 4));
			month = Integer.parseInt(dates.get(i).substring(5, 7));
			day = Integer.parseInt(dates.get(i).substring(8, headString.length()));
			Calendar temp = Calendar.getInstance();
			temp.set(year, month-1, day);
			temp.setLenient(false);
			
			if (((headCalendar.getTimeInMillis() - temp.getTimeInMillis())/DayInMillis) > 0){
				headCalendar = temp;
				headUID = uids.get(i);
			}
					
		}
		
		return headUID;
	}
	
	
	/**
	 * This function will search the DB for Order by the requested fields
	 * The Result will be sent as a Message to the client
	 * @param received - the Message object containing the fields and their requested values
	 * @param client - the client which requested the search
	 */
	public void SearchOrder(Message received, ConnectionToClient client){
		
		ArrayList<String> incommingIndex = received.GetMessageIndexes();
		ArrayList<Object> incommingData = received.GetMessageData();
		
		ResultSet rs = QueryOrder(incommingIndex, incommingData);
		
		ArrayList<Order> orderList = SeralizeResultSet(rs);

		ArrayList<String> index = new ArrayList<String>();
		index.add("INDICATOR");
		index.add("RESULTSET");
		ArrayList<Object> data = new ArrayList<Object>();
		data.add(ResultDisplay.ORDER);
		data.add(orderList);
		
		int action = Message.SEARCH_ORDER_RESPONSE;
		
		Message toSend = new Message(index, data, action);
		
		try{
			client.sendToClient(toSend);
		}catch(IOException e){
			parent.Log("[SearchOrder] IOException. " + e.getMessage());
		}
		
		
	}

	
	/**
	 * This function will close the order, and set it to CANCELED
	 * @param received - Message object contains the data
	 * @param client - the client which requested the cancel
	 */
	public void CancelOrder(Message received,ConnectionToClient client){
		
		ArrayList<Object> data = received.GetMessageData();
		
		String bid = String.valueOf(data.get(0));
		String uid = String.valueOf(data.get(1));
		
		int res = this.CloseTheOrder(bid, uid, Order.ORDER_CANCELED);
		if (res == 1) SendOutputToUser("OK","Order Canceled",Message.CANCEL_ORDER_RESPONSE,client);
		else if (res == -1) SendOutputToUser("ERROR","Order was not canceled",Message.CANCEL_ORDER_RESPONSE,client);
		
	}
	
	
	/**
	 * This Function convert a ResultSet into ArrayList<Order> for sending as a Message
	 * (ResultSet is not seralizeable)
	 * @param rs - the ResultSet to conver
	 * @return - ArrayList<Order> which conatins all the orders in the ResultSet
	 */
	public ArrayList<Order> SeralizeResultSet(ResultSet rs){
		
		ArrayList<Order> orderList = new ArrayList<Order>();

		try{
			while(rs.next()){

				//Order(int OID,int UID,int BID,String OrderDate,int status)
				Order order = new Order(rs.getInt("OID"),rs.getInt("UID"),rs.getInt("BID"),rs.getString("ORDER_DATE"),rs.getInt("STATUS"));
				orderList.add(order);
			}
			
			return orderList;
		}catch(SQLException e){
			parent.Log("[SeralizeResultSet] SQLException. " + e.getMessage());
		}
		return null;

	}
	
	
	/**
	 * This function query the DB for the bid, and returns the number of open orders
	 * @param bid - the BID to look for
	 * @return number of open orders
	 */
	public int getOpenOrdersForBook(String bid){
		String query = "SELECT * FROM orders WHERE BID = " + bid + " AND STATUS + " + Order.ORDER_OPEN;
		
		int count = 0;
		try {
			ResultSet rs = DB.Query(query);

			while(rs.next()){
				count++;
			}
			
			return count;
		}catch (SQLException e) {
			parent.Log("[getOpenOrdersForBook] SQLException. " + e.getMessage());
		}catch (NullPointerException e) {
			parent.Log("[getOpenOrdersForBook] NullPointerException. " + e.getMessage());
		}
		
		return -1;
	}
	
	
	/**
	 * This function query the DB for the bid,uid and verifies if the book is ordered for that user
	 * if so - the function will close it
	 * @param bid - the BID to look for
	 * @param uid - the UID to look for
	 * @return 1 for success, 0 no loans, -1 failure
	 */
	public int CloseTheOrder(String bid,String uid,int reason){
		String query = "SELECT * FROM orders WHERE BID = " + bid + " AND UID = " + uid + " AND STATUS = " + Order.ORDER_OPEN;
		
		int count = 0;
		ResultSet rs = DB.Query(query);
		try {
			while(rs.next()){
				count++;
			}
			if (count > 0){
				if (DB.Update("UPDATE orders set STATUS = " + reason + " WHERE BID = " + bid + " AND UID = " + uid + " AND STATUS = " + Order.ORDER_OPEN) == 1){
					return 1;
				}else return -1;
			}else return 0;
			
		}catch (SQLException e) {
			parent.Log("[CloseTheOrder] SQLException. " + e.getMessage());
		}
		
		return -1;
	}
	
	
	
	
	/**
	 * This function is private, meant to query the actual DB for the fields and return the ResultSet
	 * @param fields - the fields to query by
	 * @param data - the data to query by
	 * @return ResultSet which contains all qualified tuples
	 */
	private ResultSet QueryOrder(ArrayList<String> fields,ArrayList<Object> data){
		
		try{
			String query = "SELECT * FROM orders";
			if (fields.size() > 0) {
				
				query += " WHERE " + (String)fields.get(0) + " = '" + (String)data.get(0) + "'";


				for(int i=1; i < fields.size(); i++){
					query += " AND " + (String)fields.get(i) + " = '" + (String)data.get(i) + "'";
				}
				query += " AND STATUS = " + Order.ORDER_OPEN + ";";
			}
			
			return DB.Query(query);
			
		}catch(ClassCastException e){
			parent.Log("[QueryOrder] ClassCastException. " + e.getMessage());
		}
		
		return null;
	}
	
	/**
	 * This Function creates the Order object in the DB
	 * @param order - the Message object containing the order's information
	 * @param client - Client which created the order
	 * @return 1 for valid, 0 for error
	 */
	public int CreateOrder(Message order,ConnectionToClient client){
				
		ArrayList<Object> data = order.GetMessageData();
				
		String UID = (String)data.get(1);
		String BID = (String)data.get(0);
		
		String query = "SELECT * FROM orders WHERE UID = " + UID + " AND BID = " + BID + 
				" AND STATUS = " + Order.ORDER_OPEN + ";";
		
		ResultSet rs = DB.Query(query);
		
		BookController bookController = new BookController(parent, DB);
		MemberController memberController = new MemberController(parent, DB);
		
		
		if (!memberController.ValidateMember(UID)){
			SendOutputToUser("ERROR","No such member",Message.CREATE_ORDER_RESPONSE,client);
			return 0;
			
		}else if (!bookController.ValidateBook(BID)){
			SendOutputToUser("ERROR","No such book",Message.CREATE_ORDER_RESPONSE,client);
			return 0;
		}
		
		if (!memberController.getMemberCardStatus(UID)) {
			SendOutputToUser("ERROR","Student membercard status is not valid",Message.CREATE_ORDER_RESPONSE,client);
			return 0;
		}
		
		int copies = bookController.getAvailableCopies(BID);
		
		if (copies > 0){
			int orders = getOpenOrdersForBook(BID);
			if (orders == copies){
				SendOutputToUser("ERROR","All available copies has been ordered.",Message.CREATE_ORDER_RESPONSE,client);
				return 0;
			}
		}else{
			SendOutputToUser("ERROR","No available copies",Message.CREATE_ORDER_RESPONSE,client);
			return 0;
		}
		
		
		//inserted data is valid, and there are available copies - verify if order is not already placed
		try{
			while(rs.next()){
				SendOutputToUser("ERROR","Order already exist",Message.CREATE_ORDER_RESPONSE,client);
				return 0;
			}
			
			//Loan does not exist, OK to add!
			Calendar today = Calendar.getInstance();
			String currDate = today.get(Calendar.YEAR) + "-";
			if (today.get(Calendar.MONTH) < 10) currDate += "0" + (today.get(Calendar.MONTH) + 1) + "-";
			else currDate += (today.get(Calendar.MONTH) + 1) + "-";
			if (today.get(Calendar.DAY_OF_MONTH) < 10) currDate += "0" + today.get(Calendar.DAY_OF_MONTH);
			else currDate += today.get(Calendar.DAY_OF_MONTH);

			DB.Update("INSERT INTO ORDERS " +
					"(OID,UID,BID,ORDER_DATE,STATUS) VALUES" +
					"(null,'" + (String)data.get(1) + "','" + (String)data.get(0) + "','" + currDate + "','" + Order.ORDER_OPEN + "');");
			
			SendOutputToUser("OK","Order Created",Message.CREATE_ORDER_RESPONSE,client);

			return 1;
			
		}catch(SQLException e){
			parent.Log("[CreateOrder] SQLException. " + e.getMessage());
		}catch(NullPointerException e){
			parent.Log("[CreateOrder] NullPointerException. " + e.getMessage());
		}catch(ClassCastException e){
			parent.Log("[CreateOrder] ClassCastException. " + e.getMessage());
		}
		return 0;

	}
	
	/**
	 * This message will handle message output to the user
	 * (When only needs to send a text message without extra data)
	 * @param status - String - "OK" or "ERROR"
	 * @param message - String - containing the message
	 * @param action - int - the message type
	 * @param client - the client to send to
	 */
	private void SendOutputToUser(String status,String message,int action,ConnectionToClient client){
		
		ArrayList<String> indexOUT = new ArrayList<String>();
		ArrayList<Object> dataOUT = new ArrayList<Object>();
		
		indexOUT.add(status);
		dataOUT.add(message);
		Message toSend = new Message(indexOUT,dataOUT,action);
		try{
			client.sendToClient(toSend);	
		}catch(IOException e){
			parent.Log("[SendOutputToUser] IOException. " + e.getMessage());
		}

	}

}
